﻿// <copyright file="CommonHeader.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// This class represents the common container header used by all volume file containers.
    /// </summary>
    public class CommonHeader
    {
        /// <summary>
        /// Indicates the type of the container.
        /// </summary>
        /// <remarks>
        /// The opaque data member of the container points to different data structures
        /// depending on this value.
        /// </remarks>
        private ContainerType typeId;

        /// <summary>
        /// Identifies the particular version number of the data within this container.
        /// </summary>
        /// <remarks>
        /// The version number and type ID uniquely identify the specific format usage information
        /// for the container data. This number is unique to App-V FileSystem and is changed only
        /// when the format of the container is changed.
        /// </remarks>
        private uint version;

        /// <summary>
        /// The version ID of a file.
        /// </summary>
        /// <remarks>
        /// It is specific to the files that are created as part of the package initialization.
        /// It is used during the package upgrade only to determine if a file has been changed
        /// and needs to be upgraded.
        /// </remarks>
        private uint generationId;

        /// <summary>
        /// Indicates the parent directory container (the ContainerIndex of a container)
        /// where additional type specific information of this container is located.
        /// </summary>
        /// <example>
        /// If a container points to a directory with ContainerIndex 30 and the parent directory
        /// of this directory has ContainerIndex 15, then PrimaryDirectoryId of this container
        /// will have value 15.
        /// </example>
        /// <remarks>
        /// A value of 0 implies that there is no parent associated with this container.
        /// </remarks>
        private uint primaryDirectoryId;

        /// <summary>
        /// A unique file identifier.
        /// </summary>
        /// <remarks>
        /// It is specific to files/directories that are created as part of package initialization,
        /// and used to find a specific Container by its GUID rather than by its name.
        /// </remarks>
        private Guid fileGuid;

        /// <summary>
        /// Indicates a package container (the ContainerIndex of a package container) that this
        /// file or directory (pointed to by this container) belongs to.
        /// </summary>
        /// <remarks>
        /// A value of 0 implies that this container is not associated with any package.
        /// </remarks>
        private uint packageId;

        /// <summary>
        /// An ASCII type name buffer used for debugging purposes.
        /// </summary>
        private string typeName;

        /// <summary>
        /// The number of bytes used for the container type-specific information which is
        /// referenced by the Data member of the container.
        /// </summary>
        private uint infoDataLength;

        /// <summary>
        /// Checksum on the ContainerHdr which is an in-memory data structure. It does not cover
        /// data outside the container header and is present only to protect against certain types
        /// of runtime system failures.
        /// </summary>
        private string checkSum;

        /// <summary>
        /// Offset into the directory entry table used for a quick lookup of the container
        /// metadata structure within a parent container.
        /// </summary>
        private uint metadataIndex;

        /// <summary>
        /// Offset into Package File Table used for a quick lookup of the container reference
        /// within a package container.
        /// </summary>
        private uint packageDataIndex;

        /// <summary>
        /// Index within the Container Table which is an in-memory data structure.
        /// </summary>
        /// <remarks>
        /// For an on-disk Container structure, this value represents the sequence number that
        /// has been assigned to this container. This value is unique per container and used as
        /// a primary identifier.
        /// </remarks>
        private uint containerIndex;

        /// <summary>
        /// An index in the volume file, where this container resides.
        /// </summary>
        /// <remarks>
        /// This value is used to locate and update an on-disk container block.
        /// </remarks>
        private uint containerFlushIndex;

        /// <summary>
        /// A security descriptor offset in the Security Table (Please see Security Descriptor Container for additional information).
        /// </summary>
        private string securityDescriptorIndex;

        /// <summary>
        /// Indicates the format of the data storage region.
        /// </summary>
        /// <remarks>
        /// The format can be a bitmap of any of the following combinations:
        /// FORMAT_RAW and FORMAT_BLOCKMAP
        /// FORMAT_RAW and FORMAT_INLINE
        /// </remarks>
        private DataStorageType dataStorageType;
        
        /// <summary>
        /// This value is reserved.
        /// </summary>
        private byte[] reserved;

        /// <summary>
        /// Bit mask of persistent flags that states additional information about the container.
        /// </summary>
        private ContainerFlags flags;
        
        /// <summary>
        /// Bit mask of persistent flags that states additional information about the data structures
        /// pointed to by this container.
        /// </summary>
        private SoftricityFileTypeFlags softricityFileTypeFlags;

        /// <summary>
        /// Initializes a new instance of the CommonHeader class.
        /// </summary>
        /// <param name="binaryStreamReader">
        /// Contains the raw container header data from the volume file.
        /// </param>
        public CommonHeader(System.IO.BinaryReader binaryStreamReader)
            : this()
        {
            this.ReadContainer(binaryStreamReader);
        }

        /// <summary>
        /// Prevents a default instance of the CommonHeader class from being created.
        /// </summary>
        private CommonHeader()
        {
            this.reserved = new byte[2];
            for (int i = 0; i < this.reserved.Length; i++)
            {
                this.reserved[i] = 0;
            }
        }
        
        /// <summary>
        /// Gets a value indicating the type of a container.
        /// </summary>
        public ContainerType TypeId
        {
            get { return this.typeId; }
        }

        /// <summary>
        /// Gets the version number of the data within the container.
        /// </summary>
        public uint Version
        {
            get { return this.version; }
        }

        /// <summary>
        /// Gets the version ID of a file.
        /// </summary>
        public uint GenerationID
        {
            get { return this.generationId; }
        }

        /// <summary>
        /// Gets a value indicating the parent directory container (the ContainerIndex
        /// of a container) where additional type-specific information of this container is located.
        /// </summary>
        /// <example>
        /// If a container points to a directory with ContainerIndex 30 and the parent directory
        /// of this directory has ContainerIndex 15, then PrimaryDirectoryId of this container
        /// will have value 15.
        /// </example>
        /// <remarks>
        /// A value of 0 implies that there is no parent associated with this container.
        /// </remarks>
        public uint PrimaryDirectoryID
        {
            get { return this.primaryDirectoryId; }
        }

        /// <summary>
        /// Gets a unique file identifier.
        /// </summary>
        /// <remarks>
        /// It is specific to files/directories that are created as part of package initialization,
        /// and used to find a specific Container by its GUID rather than by its name.
        /// </remarks>
        public Guid FileGuid
        {
            get { return this.fileGuid; }
        }

        /// <summary>
        /// Gets a value indicating a package container (the ContainerIndex of a package container)
        /// that this file or directory (pointed to by this container) belongs to.
        /// </summary>
        /// <remarks>
        /// A value of 0 implies that this container is not associated with any package.
        /// </remarks>
        public uint PackageID
        {
            get { return this.packageId; }
        }

        /// <summary>
        /// Gets an ASCII type name used for debugging purposes.
        /// </summary>
        public string TypeName
        {
            get { return this.typeName; }
        }

        /// <summary>
        /// Gets the number of bytes used for the container type-specific information which is
        /// referenced by the Data member of the container.
        /// </summary>
        public uint InfoDataLength
        {
            get { return this.infoDataLength; }
        }

        /// <summary>
        /// Gets a checksum on the ContainerHdr which is an in-memory data structure.
        /// </summary>
        /// <remarks>
        /// This value does not cover data outside the container header and is present only to
        /// protect against certain types of runtime system failures.
        /// </remarks>
        public string CheckSum
        {
            get { return this.checkSum; }
        }

        /// <summary>
        /// Gets the offset into the directory entry table used for a quick lookup of the container
        /// metadata structure within a parent container.
        /// </summary>
        public uint MetadataIndex
        {
            get { return this.metadataIndex; }
        }

        /// <summary>
        /// Gets the offset into Package File Table used for a quick lookup of the container reference
        /// within a package container.
        /// </summary>
        public uint PackageDataIndex
        {
            get { return this.packageDataIndex; }
        }

        /// <summary>
        /// Gets the index within the Container Table which is an in-memory data structure.
        /// </summary>
        /// <remarks>
        /// For an on-disk Container structure, this value represents the sequence number that
        /// has been assigned to this container. This value is unique per container and used as
        /// a primary identifier.
        /// </remarks>
        public uint ContainerIndex
        {
            get { return this.containerIndex; }
        }

        /// <summary>
        /// Gets an index in the volume file, where this container resides.
        /// </summary>
        /// <remarks>
        /// This value is used to locate and update an on-disk container block.
        /// </remarks>
        public uint ContainerFlushIndex
        {
            get { return this.containerFlushIndex; }
        }

        /// <summary>
        /// Gets a security descriptor offset in the Security Table.
        /// </summary>
        /// <remarks>
        /// Please see Security Descriptor Container for additional information.
        /// </remarks>
        public string SecurityDescriptorIndex
        {
            get { return this.securityDescriptorIndex; }
        }

        /// <summary>
        /// Gets a value indicating the format of the data storage region.
        /// </summary>
        /// <remarks>
        /// The format can be a bitmap of any of the following combinations:
        /// FORMAT_RAW and FORMAT_BLOCKMAP
        /// FORMAT_RAW and FORMAT_INLINE
        /// </remarks>
        public DataStorageType StorageType
        {
            get { return this.dataStorageType; }
        }

        /// <summary>
        /// Gets a value that states additional information about the container.
        /// </summary>
        public ContainerFlags Flags
        {
            get { return this.flags; }
        }

        /// <summary>
        /// Gets a value that states additional information about the data structures
        /// pointed to by this container.
        /// </summary>
        public SoftricityFileTypeFlags SoftricityFlags
        {
            get { return this.softricityFileTypeFlags; }
        }

        /// <summary>
        /// Reads the actual data for this container from the file.
        /// </summary>
        /// <param name="streamReader">
        /// Contains the raw container header data from the volume file.
        /// </param>
        protected void ReadContainer(System.IO.BinaryReader streamReader)
        {
            uint rawUInt;
            ushort rawUShort;

            rawUInt = streamReader.ReadUInt32();
            this.typeId = (ContainerType)rawUInt;

            this.version = streamReader.ReadUInt32();
            this.generationId = streamReader.ReadUInt32();
            this.primaryDirectoryId = streamReader.ReadUInt32();

            byte[] guidBytes = streamReader.ReadBytes(16);
            this.fileGuid = new Guid(guidBytes);

            this.packageId = streamReader.ReadUInt32();

            byte[] stringBytes;
            System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

            stringBytes = streamReader.ReadBytes(16);
            this.typeName = encoding.GetString(stringBytes);

            this.infoDataLength = streamReader.ReadUInt32();

            stringBytes = streamReader.ReadBytes(4);
            this.checkSum = encoding.GetString(stringBytes);

            this.metadataIndex = streamReader.ReadUInt32();

            this.packageDataIndex = streamReader.ReadUInt32();
            this.containerIndex = streamReader.ReadUInt32();
            this.containerFlushIndex = streamReader.ReadUInt32();

            stringBytes = streamReader.ReadBytes(4);
            this.securityDescriptorIndex = encoding.GetString(stringBytes);

            rawUShort = streamReader.ReadUInt16();
            this.dataStorageType = (DataStorageType)rawUShort;

            this.reserved = streamReader.ReadBytes(2);

            rawUInt = streamReader.ReadUInt32();
            this.flags = (ContainerFlags)rawUInt;

            rawUShort = streamReader.ReadUInt16();
            this.softricityFileTypeFlags = (SoftricityFileTypeFlags)rawUShort;
        }
    }
}
